﻿using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Media;

namespace System.Windows.Controls
{
    public class Scale : FrameworkElement
    {
        private static readonly DependencyProperty TypefaceProperty = DependencyProperty.Register("Typeface",
                                                                                                  typeof (string),
                                                                                                  typeof (Scale),
                                                                                                  new PropertyMetadata(
                                                                                                      OnTypefaceChanged));

        private static readonly DependencyProperty FontSizeProperty = DependencyProperty.Register("FontSize",
                                                                                                  typeof (int),
                                                                                                  typeof (Scale),
                                                                                                  new PropertyMetadata(
                                                                                                      OnFontSizeChanged));

        public static DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof (int),
                                                                                      typeof (Scale),
                                                                                      new PropertyMetadata(
                                                                                          OnRadiusChanged));

        public static DependencyProperty TextRadiusProperty = DependencyProperty.Register("TextRadius", typeof (int),
                                                                                          typeof (Scale),
                                                                                          new PropertyMetadata(
                                                                                              OnTextRadiusChanged));

        private static readonly DependencyProperty MajorTickSizeProperty = DependencyProperty.Register("MajorTickSize",
                                                                                                       typeof (int),
                                                                                                       typeof (Scale),
                                                                                                       new PropertyMetadata((int)0,OnPropertyChanged));

        private static readonly DependencyProperty MinorTickProperty = DependencyProperty.Register("MinorTick", typeof (int),
                                                                                         typeof (Scale),
                                                                                         new PropertyMetadata(
                                                                                             OnMinorTickChanged));

        private static readonly DependencyProperty MajorTickProperty = DependencyProperty.Register("MajorTick", typeof (int),
                                                                                         typeof (Scale),
                                                                                         new PropertyMetadata((int)0,
                                                                                             OnMajorTickChanged));

        private static readonly DependencyProperty StrokeThicknessProperty =
            DependencyProperty.Register("StrokeThickness", typeof (int), typeof (Scale),
                                        new PropertyMetadata(OnStrokeThicknessChanged));

        private static readonly DependencyProperty StrokeProperty = DependencyProperty.Register("Stroke", typeof (Brush),
                                                                                      typeof (Scale),
                                                                                      new PropertyMetadata(
                                                                                          OnStrokeChanged));

        private static readonly DependencyProperty LabelsProperty = DependencyProperty.Register("Labels",
                                                                                                typeof (
                                                                                                    ObservableCollection
                                                                                                    <ScaleLabel>),
                                                                                                typeof (Scale),
                                                                                                new PropertyMetadata(
                                                                                                    OnLabelsChanged));

        private static readonly DependencyProperty MajorTicksProperty = DependencyProperty.Register("MajorTicks",
                                                                                                    typeof (
                                                                                                        ObservableCollection
                                                                                                        <ScaleTick>),
                                                                                                    typeof (Scale), null);

        private Point _begin;
        private Point _end;
        private Size _size;

        public Scale()
        {
            MajorTickSize = 10;
            MajorTick = 10;
            MinorTick = 2;
            Labels = new ObservableCollection<ScaleLabel>();
            Ticks = new ObservableCollection<ScaleTick>();
            Typeface = "Verdana";
            FontSize = 10;
            Stroke = new SolidColorBrush(Colors.Black);
        }

        public Size Size
        {
            get { return _size; }
            set
            {
                if (_size != value)
                {
                    _size = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("Size"));
                }
            }
        }
        /// <summary>
        /// The font name used for the scale text
        /// </summary>
        public string Typeface
        {
            get { return (string) GetValue(TypefaceProperty); }
            set { SetValue(TypefaceProperty, value); }
        }
        /// <summary>
        /// The scale text size
        /// </summary>
        public int FontSize
        {
            get { return (int) GetValue(FontSizeProperty); }
            set { SetValue(FontSizeProperty, value); }
        }
        /// <summary>
        /// The radius at which the scale is displayed
        /// </summary>
        public int Radius
        {
            get { return (int) GetValue(RadiusProperty); }
            set { SetValue(RadiusProperty, value); }
        }
        /// <summary>
        /// The radius at which the scale text is displayed
        /// </summary>
        public int TextRadius
        {
            get { return (int) GetValue(TextRadiusProperty); }
            set { SetValue(TextRadiusProperty, value); }
        }
        /// <summary>
        /// The size of the large scale ticks
        /// </summary>
        public int MajorTickSize
        {
            get { return (int) GetValue(MajorTickSizeProperty); }
            set { SetValue(MajorTickSizeProperty, value); }
        }
        /// <summary>
        /// The minor tick interval
        /// </summary>
        public int MinorTick
        {
            get { return (int) GetValue(MinorTickProperty); }
            set { SetValue(MinorTickProperty, value); }
        }
        /// <summary>
        /// The major tick interval
        /// </summary>
        public int MajorTick
        {
            get { return (int) GetValue(MajorTickProperty); }
            set { SetValue(MajorTickProperty, value); }
        }
        /// <summary>
        /// The line thickness of the scale
        /// </summary>
        public int StrokeThickness
        {
            get { return (int) GetValue(StrokeThicknessProperty); }
            set { SetValue(StrokeThicknessProperty, value); }
        }
        /// <summary>
        /// The color of the scale
        /// </summary>
        public Brush Stroke
        {
            get { return (Brush) GetValue(StrokeProperty); }
            set { SetValue(StrokeProperty, value); }
        }

        internal Point Begin
        {
            get { return _begin; }
            set
            {
                if (_begin != value)
                {
                    _begin = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("Begin"));
                }
            }
        }

        internal Point End
        {
            get { return _end; }
            set
            {
                if (_end != value)
                {
                    _end = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("End"));
                }
            }
        }
        
        internal ObservableCollection<ScaleLabel> Labels
        {
            get { return (ObservableCollection<ScaleLabel>) GetValue(LabelsProperty); }
            set { SetValue(LabelsProperty, value); }
        }
        
        public ObservableCollection<ScaleTick> Ticks
        {
            get { return (ObservableCollection<ScaleTick>) GetValue(MajorTicksProperty); }
            set { SetValue(MajorTicksProperty, value); }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged = (s, p) => { };

        #endregion

        private static void OnTypefaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // insert your code here
        }

        private static void OnFontSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // insert your code here
        }

        private static void OnRadiusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var scale = (Scale) d;
            scale.Size = new Size((int) e.NewValue, (int) e.NewValue);
            OnPropertyChanged(d, e);
        }

        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var scale = (Scale) d;
            scale.PropertyChanged(scale, new PropertyChangedEventArgs(e.Property.Name));
        }

        private static void OnTextRadiusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            OnPropertyChanged(d, e);
        }

        private static void OnMinorTickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            OnPropertyChanged(d, e);
        }

        private static void OnMajorTickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var scale = (Scale) d;
            //scale.MinorTick = Math.Min(scale.MinorTick, scale.MajorTick);
            OnPropertyChanged(d, e);
            System.Diagnostics.Debug.WriteLine(scale.MajorTick);
        }

        private static void OnStrokeThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var scale = (Scale) d;
            foreach (ScaleTick tick in scale.Ticks)
            {
                tick.StrokeThickness = scale.StrokeThickness;
            }
        }

        private static void OnStrokeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var scale = (Scale) d;
            foreach (ScaleTick tick in scale.Ticks)
            {
                tick.Stroke = scale.Stroke;
            }
        }

        private static void OnLabelsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            
        }

        private static Point GetCoordinate(Point center, int radius, double angle)
        {
            double radians = Math.PI * angle / 180;

            return new Point(center.X + radius * (float)Math.Cos(radians),
                             center.Y - radius * (float)Math.Sin(radians));
        }

        public void UpdateTicks(Point center, int startAngle, int sweep, int min, int max)
        {
            if (MajorTick == 0)
                return;
            if (sweep == 0)
                return;
            if (max == 0)
                return;
            if (min > max)
                return;
            int numTicks = MajorTick > max ? max : MajorTick;
            Ticks.Clear();

            for (int i = 0; i <= numTicks; i++)
            {
                double delta = sweep / (double)numTicks;
                double angle = startAngle - (delta*i);
                var tick = new ScaleTick
                               {
                                   First = GetCoordinate(center, Radius, angle),
                                   Second = GetCoordinate(center, Radius - MajorTickSize, angle),
                                   Stroke = Stroke,
                                   StrokeThickness = StrokeThickness
                               };
                Ticks.Add(tick);
            }
            if (MinorTick == 0)
                return;
            for (int i = 0; i <= MinorTick;i++)
            {
                double delta = sweep / (double)MinorTick;
                double angle = startAngle - (delta * i);
                var tick = new ScaleTick
                               {
                                   First = GetCoordinate(center, Radius, angle),
                                   Second = GetCoordinate(center, Radius - (MajorTickSize/2), angle),
                                   Stroke = Stroke,
                                   StrokeThickness = StrokeThickness/2
                               };
                Ticks.Add(tick);
            }
            
        }

        public void UpdateLabels(Point center, int startAngle, int sweep, int min, int max)
        {
            if (MajorTick == 0)
                return;
            if (sweep == 0)
                return;
            if (max == 0)
                return;
            if (min > max)
                return;
            int numTicks = MajorTick > max? max: MajorTick;
            
            Labels.Clear();
            for (int i = 1; i < numTicks; i++)
            {
                int radius = TextRadius;
                int text = (((max - min) / numTicks) * i) + min;
                var formattedText = new FormattedText(text.ToString(), CultureInfo.CurrentCulture,
                                                      FlowDirection.LeftToRight, new Typeface(Typeface), FontSize,
                                                      Stroke);
                double delta = sweep / (double)numTicks;
                double angle = startAngle - (delta * i);
                Point location = GetCoordinate(center, radius, angle);

                location.X -= formattedText.WidthIncludingTrailingWhitespace/2;
                location.Y -= formattedText.Height/2;

                var label = new ScaleLabel {Location = location, Text = text.ToString(), Stroke = Stroke};
                Labels.Add(label);
            }
            UpdateTicks(center, startAngle, sweep, min, max);
            //PropertyChanged(this,new PropertyChangedEventArgs("Labels"));
        }

        public void Refresh(Point center, int startAngle, int sweep, int min, int max)
        {
            Size = new Size(Radius, Radius);
            Begin = GetCoordinate(center, Radius, startAngle);
            End = GetCoordinate(center, Radius, startAngle - sweep);

        }

        
    }
}